package controller

import (
	"errors"
	"fmt"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/bluele/gcache"
)

type ServerHealthChecker interface {
	TrivyServerAvaliable(serverURL string) (bool, error)
}

func NewTrivyServerChecker(expiration *time.Duration, cache gcache.Cache, httpChecker HttpChecker) ServerHealthChecker {
	return &trivyServerHealthChecker{expiration: expiration, cache: cache, httpChecker: httpChecker}
}

type trivyServerHealthChecker struct {
	mutex       sync.RWMutex
	cache       gcache.Cache
	expiration  *time.Duration
	httpChecker HttpChecker
}

func NewHttpChecker() HttpChecker {
	return &httpChecker{}
}

// HttpChecker check health with http server
type HttpChecker interface {
	ServerHealthy(serverURL string) error
}

type httpChecker struct {
}

// TrivyServerAvaliable check if trivy server is available
// 1st cehck in cache and if not found it check for server health via http call the healthz api and update cache
func (r *trivyServerHealthChecker) TrivyServerAvaliable(serverURL string) (bool, error) {
	up, err := r.serverUpFromCache()
	if err != nil {
		if !errors.Is(err, gcache.KeyNotFoundError) {
			return false, err
		}
		err := r.checkServerUp(serverURL)
		if err != nil {
			return false, nil
		}
		return true, nil
	}
	return up, nil
}

func (r *trivyServerHealthChecker) serverUpFromCache() (bool, error) {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	serverUp, err := r.cache.Get(trivyServerUp)
	if err != nil {
		return false, err
	}
	value, ok := serverUp.(bool)
	if ok && value {
		return true, nil
	}
	return false, nil
}

func (r *trivyServerHealthChecker) checkServerUp(serverURL string) error {
	r.mutex.Lock()
	defer r.mutex.Unlock()
	err := r.httpChecker.ServerHealthy(serverURL)
	if err != nil {
		return err
	}
	_ = r.cache.SetWithExpire(trivyServerUp, true, *r.expiration)
	return nil

}

// ServerHealthy make http call the trivy server 'healthz' api for it availability
func (r *httpChecker) ServerHealthy(serverURL string) error {
	resp, err := http.Get(fmt.Sprintf("%s/%s", strings.TrimSuffix(serverURL, "/"), "healthz"))
	if err != nil {
		return err
	}
	defer func() {
		_ = resp.Body.Close()
	}()
	if resp.StatusCode == http.StatusOK {
		return nil
	}
	return errors.New("trivy server is not up and running")
}
